#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <mpi.h>

typedef struct pixel {
	uint8_t r;
	uint8_t g;
	uint8_t b;
	uint8_t a;
} pixel;

pixel *image, *new_image;
//int16_t width=0, height=0;
int32_t width=0, height=0;

pixel smooth(int pos_x, int pos_y) {
	pixel out_pixel;
	int count=0, sum_r=0, sum_g=0, sum_b=0, sum_a=0;
	int i,j;

	for(i=-2;i<=2;i++){
		if ((pos_x+i<0) || (pos_x+i>=height)) continue;
		for(j=-2;j<=2;j++){
			if ((pos_y+j<0) || (pos_y+j>=width)) continue;
			count++;
			sum_r += image[(pos_x+i)*width+pos_y+j].r;
			sum_g += image[(pos_x+i)*width+pos_y+j].g;
			sum_b += image[(pos_x+i)*width+pos_y+j].b;
			sum_a += image[(pos_x+i)*width+pos_y+j].a;
		}
	}
	out_pixel.r = sum_r/count;
	out_pixel.g = sum_g/count;
	out_pixel.b = sum_b/count;
	out_pixel.a = sum_a/count;

	return out_pixel;
}

int main(int argc, char* argv[]) {
	FILE *input_file=NULL, *output_file=NULL;
	int i, i_2,j, tmp;
	uint8_t *buffer;
	struct timespec start_time, end_time, total_time;
	
	pixel *local_image;
	int comm_sz, my_rank, offset, count, begin, end;

	clock_gettime(CLOCK_MONOTONIC, &start_time);

	//Ler entrada
	input_file = fopen("image.in","r");
	output_file = fopen("image.out","w");

	fseek(input_file, 0xA, SEEK_SET);
	fread(&tmp, 4, 1, input_file);

	fseek(input_file, 0x12, SEEK_SET);
	//fread(&width, 2, 1, input_file);
	//fread(&height, 2, 1, input_file);
	fread(&width, 4, 1, input_file);
	fread(&height, 4, 1, input_file);
	
	image = (pixel *) malloc(width*height*sizeof(pixel));
	new_image = (pixel *) malloc(width*height*sizeof(pixel));
	buffer = (uint8_t *) malloc(tmp*sizeof(uint8_t));

	fseek(input_file, 0, SEEK_SET);
	fread(buffer, sizeof(uint8_t), tmp, input_file);

	//fseek(input_file, tmp, SEEK_SET);
	fread(image, sizeof(pixel), width*height, input_file);

	MPI_Init(NULL,NULL);
	MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
	MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

	count = (height)/comm_sz;
	offset = (height) % comm_sz;
	begin = count*my_rank;
	end = count*(my_rank+1)-1;
	if (my_rank<offset){
		begin += my_rank;
		end += my_rank+1;
	}
	else {
		begin += offset;
		end += offset;
	}

	local_image = (pixel *) malloc(width*(end-begin)*sizeof(pixel));
	//**Considera-se que int é 32 bits
	//MPI_Bcast(image, height*width, MPI_INT, 0, MPI_COMM_WORLD);
	
	//Aplicar estêncil
	for(i=begin, i_2=0; i<end; i++, i_2++) {
		for(j=0; j<width; j++) {
			local_image[i_2*width+j] = smooth(i, j);
		}
	}

	if (my_rank == 0) {
		//count -= offset;
		//image += offset*4*width;
		//local_image = (pixel *) malloc(count*sizeof(pixel));

		//Considera-se que int é 32 bits
		MPI_Gather(local_image, count*width, MPI_INT, new_image, count*width, MPI_INT, 0, MPI_COMM_WORLD);
		
		//image -= offset*4*width;
		//count += offset;
	}
	else {
		MPI_Gather(local_image, count*width, MPI_INT, new_image, count*width, MPI_INT, 0, MPI_COMM_WORLD);
	}
	
	free(local_image);

	MPI_Finalize();

	//Escrever saída
	//fwrite(&width, 2, 1, output_file);
	//fwrite(&height, 2, 1, output_file);
	fwrite(buffer, sizeof(uint8_t), tmp, output_file);
	fwrite(new_image, sizeof(struct pixel), width*height, output_file);
	
	fclose(input_file);
	fclose(output_file);
	//**Necessário? Gasta tempo. Podemos verificar no profiler.
	free(image);
	free(new_image);

	//Calculando tempo total.
	clock_gettime(CLOCK_MONOTONIC, &end_time);
	total_time.tv_sec = end_time.tv_sec - start_time.tv_sec;
	total_time.tv_nsec = end_time.tv_nsec - start_time.tv_nsec;
	if (total_time.tv_nsec < 0) {
		total_time.tv_sec--;
		total_time.tv_nsec+=1.e9;
	}
	printf("Time:%ld.%lds\n",total_time.tv_sec, total_time.tv_nsec);

	return 0;
}
